package vn.vfriends.id.jpa.controller;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.persistence.*;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import vn.vfriends.id.jpa.controller.exceptions.NonexistentEntityException;
import vn.vfriends.id.jpa.controller.exceptions.RollbackFailureException;
import vn.vfriends.id.jpa.controller.exceptions.ViolationException;
import vn.vfriends.id.jpa.entity.Group;
import vn.vfriends.id.jpa.entity.Role;
import vn.vfriends.id.jpa.entity.User;

/**
 *
 * @author tuan@vfriends.vn
 */
public class GroupJpaController implements Serializable {

    @Resource
    private UserTransaction utx = null;
    @PersistenceUnit(unitName = "vFriendsIdPU")
    private EntityManagerFactory emf = null;
    @Inject
    private Validator validator;

    public EntityManager getEntityManager() {
        return emf.createEntityManager();
    }

    public void create(Group group) throws RollbackFailureException, Exception {
        if (group.getUsers() == null) {
            group.setUsers(new ArrayList<User>());
        }
        EntityManager em = null;
        try {
            Set<ConstraintViolation<Group>> violations = this.validator.validate(group);
            if (violations.size() > 0) {
                ViolationException exception = new ViolationException(violations);
                throw exception;
            }

            utx.begin();
            em = getEntityManager();
            //  Users
            List<User> attachedUsers = new ArrayList<User>();
            for (User userToAttach : group.getUsers()) {
                userToAttach = em.getReference(userToAttach.getClass(), userToAttach.getId());
                attachedUsers.add(userToAttach);
            }
            group.setUsers(attachedUsers);
            // Roles
            List<Role> attachedRoles = new ArrayList<Role>();
            for (Role roleToAttach : group.getRoles()) {
                roleToAttach = em.getReference(roleToAttach.getClass(), roleToAttach.getId());
                attachedRoles.add(roleToAttach);
            }
            group.setRoles(attachedRoles);
            
            em.persist(group);
            for (User user : group.getUsers()) {
                user.getGroups().add(group);
                user = em.merge(user);
            }
            utx.commit();
        } catch (ViolationException ve) {
            throw ve;
        } catch (Exception ex) {
            try {
                if (utx.getStatus() == Status.STATUS_ACTIVE) {
                    utx.rollback();
                }
            } catch (Exception re) {
                throw new RollbackFailureException("An error occurred attempting to roll back the transaction.", re);
            }
            throw ex;
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

    public void edit(Group group) throws NonexistentEntityException, RollbackFailureException, Exception {
        EntityManager em = null;
        try {
            Set<ConstraintViolation<Group>> violations = this.validator.validate(group);
            if (violations.size() > 0) {
                ViolationException exception = new ViolationException(violations);
                throw exception;
            }
            utx.begin();
            em = getEntityManager();
            Group persistentGroup = em.find(Group.class, group.getId());
            List<User> oldUsers = persistentGroup.getUsers();
            List<User> newUsers = group.getUsers();
            List<User> attachedNewUsers = new ArrayList<User>();
            for (User newUserToAttach : newUsers) {
                newUserToAttach = em.getReference(newUserToAttach.getClass(), newUserToAttach.getId());
                attachedNewUsers.add(newUserToAttach);
            }
            newUsers = attachedNewUsers;
            group.setUsers(newUsers);
            
            List<Role> oldRoles = persistentGroup.getRoles();
            List<Role> newRoles = group.getRoles();
            List<Role> attachedNewRoles = new ArrayList<Role>();
            for (Role newRoleToAttach : newRoles) {
                newRoleToAttach = em.getReference(newRoleToAttach.getClass(), newRoleToAttach.getId());
                attachedNewRoles.add(newRoleToAttach);
            }
            newRoles = attachedNewRoles;
            group.setRoles(newRoles);
            
            group = em.merge(group);
            
            for (User oldUser : oldUsers) {
                if (!newUsers.contains(oldUser)) {
                    oldUser.getGroups().remove(group);
                    oldUser = em.merge(oldUser);
                }
            }
            for (User newUser : newUsers) {
                if (!oldUsers.contains(newUser)) {
                    newUser.getGroups().add(group);
                    newUser = em.merge(newUser);
                }
            }
            
            for (Role oldRole : oldRoles) {
                if (!newRoles.contains(oldRole)) {
                    oldRole.getGroups().remove(group);
                    oldRole = em.merge(oldRole);
                }
            }
            for (Role newRole : newRoles) {
                if (!oldRoles.contains(newRole)) {
                    newRole.getGroups().add(group);
                    newRole = em.merge(newRole);
                }
            }
            
            utx.commit();
        } catch (ViolationException ve) {
            throw ve;
        } catch (Exception ex) {
            try {
                if (utx.getStatus() == Status.STATUS_ACTIVE) {
                    utx.rollback();
                }
            } catch (Exception re) {
                throw new RollbackFailureException("An error occurred attempting to roll back the transaction.", re);
            }
            String msg = ex.getLocalizedMessage();
            if (msg == null || msg.length() == 0) {
                Long id = group.getId();
                if (find(id) == null) {
                    throw new NonexistentEntityException("The group with id " + id + " no longer exists.");
                }
            }
            throw ex;
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

    public void destroy(Long id) throws NonexistentEntityException, RollbackFailureException, Exception {
        EntityManager em = null;
        try {
            utx.begin();
            em = getEntityManager();
            Group group;
            try {
                group = em.getReference(Group.class, id);
                group.getId();
            } catch (EntityNotFoundException enfe) {
                throw new NonexistentEntityException("The group with id " + id + " no longer exists.", enfe);
            }
            List<User> users = group.getUsers();
            for (User user : users) {
                user.getGroups().remove(group);
                user = em.merge(user);
            }
            List<Role> roles = group.getRoles();
            for (Role role : roles) {
                role.getGroups().remove(group);
                role = em.merge(role);
            }
            em.remove(group);
            utx.commit();
        } catch (Exception ex) {
            try {
                if (utx.getStatus() == Status.STATUS_ACTIVE) {
                    utx.rollback();
                }
            } catch (Exception re) {
                throw new RollbackFailureException("An error occurred attempting to roll back the transaction.", re);
            }
            throw ex;
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

    public List<Group> find() {
        return find(true, -1, -1);
    }

    public List<Group> find(int maxResults, int firstResult) {
        return find(false, maxResults, firstResult);
    }

    private List<Group> find(boolean all, int maxResults, int firstResult) {
        EntityManager em = getEntityManager();
        try {
            CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
            cq.select(cq.from(Group.class));
            Query q = em.createQuery(cq);
            if (!all) {
                q.setMaxResults(maxResults);
                q.setFirstResult(firstResult);
            }
            return q.getResultList();
        } finally {
            em.close();
        }
    }

    public Group find(Long id) {
        EntityManager em = getEntityManager();
        try {
            return em.find(Group.class, id);
        } finally {
            em.close();
        }
    }

    public Group findByName(String name) {
        EntityManager em = getEntityManager();
        try {
            Query query = em.createNamedQuery("Group.findByName", Group.class);
            query.setParameter("name", name);
            List<Group> groupList = query.getResultList();
            if (groupList != null && !groupList.isEmpty()) {
                return groupList.get(0);
            } else {
                return null;
            }
        } finally {
            em.close();
        }
    }
    
    public Group findByCode(Group.GroupCode code) {
        EntityManager em = getEntityManager();
        try {
            Query query = em.createNamedQuery("Group.findByCode", Group.class);
            query.setParameter("code", code);
            List<Group> groupList = query.getResultList();
            if (groupList != null && !groupList.isEmpty()) {
                return groupList.get(0);
            } else {
                return null;
            }
        } finally {
            em.close();
        }
    }
    
    public int getCount() {
        EntityManager em = getEntityManager();
        try {
            CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
            Root<Group> rt = cq.from(Group.class);
            cq.select(em.getCriteriaBuilder().count(rt));
            Query q = em.createQuery(cq);
            return ((Long) q.getSingleResult()).intValue();
        } finally {
            em.close();
        }
    }
}
